﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Collections;
using System.Collections.ObjectModel;

namespace Wimat.Collections
{
    public class SplitCollectionView<TSource, TResult>
    {
        #region Domain
        private ReadOnlyObservableCollection<TSource> _Domain;
        public ReadOnlyObservableCollection<TSource> Domain
        {
            get { return _Domain; }
            set
            {
                _Domain = value;
                _Source = new ListCollectionView(_Domain);
                _Source.Filter = (x) =>
                    {
                        return x == null
                            || ((DomainFilter == null || DomainFilter((TSource)x))
                                && (SourceContainedInResults == null || !SourceContainedInResults((TSource)x, _resultsReadOnly)));
                    };
            }
        }
        #endregion

        #region Source
        private ListCollectionView _Source;

        public ListCollectionView Source
        {
            get
            {
                return _Source;
            }
        }
        #endregion

        #region Results
        private ObservableCollection<TResult> _results;
        private ReadOnlyCollection<TResult> _resultsReadOnly;
        private ListCollectionView _ResultView;
        public ListCollectionView Result
        {
            get
            {
                InitResultsIfNull();
                return _ResultView;
            }
        }
        private void InitResultsIfNull()
        {
            if (_results == null)
            {
                _results = new ObservableCollection<TResult>();
                _resultsReadOnly = new ReadOnlyCollection<TResult>(_results);
                _ResultView = new ListCollectionView(_resultsReadOnly);
                _RemovedResults = new ObservableCollection<TResult>();
                _RemovedResultsReadOnly = new ReadOnlyObservableCollection<TResult>(_RemovedResults);
            }
        }
        public IEnumerable<TResult> OriginalResults
        {
            set
            {
                InitResultsIfNull();
                foreach (var t in value)
                {
                    _results.Add(t);
                }
            }
        }
        private ObservableCollection<TResult> _RemovedResults;
        private ReadOnlyObservableCollection<TResult> _RemovedResultsReadOnly;
        public ReadOnlyObservableCollection<TResult> RemovedResults
        {
            get
            {
                InitResultsIfNull();
                return _RemovedResultsReadOnly;
            }
        }

        #endregion

        #region External functions
        private Predicate<TSource> _DomainFilter;
        public Predicate<TSource> DomainFilter
        {
            get { return _DomainFilter; }
            set
            {
                _DomainFilter = value;
                if (_Source != null)
                {
                    _Source.Refresh();
                }
            }
        }
        private Func<TSource, TResult> _SourceToResult;
        public Func<TSource, TResult> SourceToResult
        {
            get { return _SourceToResult; }
            set
            {
                _SourceToResult = value;
                if (_SourceContainedInResults == null)
                {
                    _SourceContainedInResults = (item, results) =>
                    {
                        return results.Contains(_SourceToResult(item));
                    };
                }
            }
        }
        private Func<TSource, ReadOnlyCollection<TResult>, bool> _SourceContainedInResults;
        public Func<TSource, ReadOnlyCollection<TResult>, bool> SourceContainedInResults
        {
            get { return _SourceContainedInResults; }
            set
            {
                _SourceContainedInResults = value;
                if (_Source != null)
                {
                    _Source.Refresh();
                }
            }
        }
        public Action<TResult> OnResultRemoved
        {
            get;
            set;
        }
        #endregion

        #region Manipulation methods
        public void MoveCurrentToResults()
        {
            if (_Source.CurrentItem == null) return;
            var c = (TSource)_Source.CurrentItem;
            _results.Add(SourceToResult(c));
            _ResultView.Refresh();
            _Source.Refresh();
        }
        public void RemoveCurrentFromResults()
        {
            if (_ResultView.CurrentItem == null) return;

            var c = (TResult)_ResultView.CurrentItem;
            RemoveFromResults(c);
        }
        public void RemoveFromResults(TResult r)
        {
            bool succeeded = _results.Remove(r);
            if (!succeeded) throw new ArgumentException();

            _RemovedResults.Add(r);
            if (OnResultRemoved != null)
            {
                OnResultRemoved(r);
            }
            _ResultView.Refresh();
            _Source.Refresh();
        }
        #endregion

        #region .ctor
        public SplitCollectionView()
        {
            InitResultsIfNull();
        }
        #endregion
    }
}
